#include "fh_spi_extend.h"

struct spi_advanced_info spi_info[] = 
{
	{
#if defined (SPI_QUAD_ENABLE)
		.wire_support = SPI_RX_QUAD,
#elif defined (SPI_DUAL_ENABLE)
		.wire_support = SPI_RX_DUAL,
#else
		.wire_support = 0,
#endif

#if defined (SPI_32BIT_XFER_ENABLE)
		.swap_support = SPI_32BIT_XFER,
#elif defined (SPI_16BIT_XFER_ENABLE)
		.swap_support = SPI_16BIT_XFER,
#else
		.swap_support = 0,
#endif

#if defined (CONFIG_FH_SPI_DTR)
		.dtr_support = SPI_RX_DTR,
#else
		.dtr_support = 0,
#endif

		.data_increase_support = INC_SUPPORT,
		.data_field_size = 0x1000,
		.data_reg_offset = 0x1000,
	},
	{
		.wire_support = 0,
		.swap_support = 0,
	},
};

struct spi_advanced_info *get_spi_info(unsigned int bus_no)
{
	if (bus_no > ARRAY_SIZE(spi_info))
		return NULL;

	return &spi_info[bus_no];
}

SINT32 Spi_SetSwap(UINT32 base, unsigned int value)
{
	UINT32 data = 0;
	Spi_Enable(base, SPI_DISABLE);
	data = GET_REG(REG_SPI_CCFGR(base));
	data &= ~(1<<12);
	data |= (value<<12);
	SET_REG(REG_SPI_CCFGR(base), data);
	Spi_Enable(base, SPI_ENABLE);
	return CONFIG_OK;
}

SINT32 Spi_SetWidth(UINT32 base, unsigned int value)
{
	UINT32 data = 0;
	UINT32 ret  = 0;
	Spi_Enable(base, SPI_DISABLE);
	data = GET_REG(REG_SPI_CTRLR0(base));
	ret  = GET_REG(REG_SPI_CCFGR(base));
	if (value == 32) {
		ret |= 1<<16;
		data &= ~(0x1f << 16);
		data |= ((value - 1) << 16);
	} else {
		ret &= ~(1<<16);
		data &= ~(0x0f << 0);
		data |= ((value - 1) << 0);
	}
	SET_REG(REG_SPI_CCFGR(base), ret);
	SET_REG(REG_SPI_CTRLR0(base), data);
	Spi_Enable(base, SPI_ENABLE);

	return CONFIG_OK;
}

SINT32 Spi_Clk_Masken(UINT32 base, unsigned int value)
{
	UINT32 data = 0;
	data = GET_REG(REG_SPI_CCFGR(base));
	data &= ~(1<<15);
	data |= (value<<15);
	SET_REG(REG_SPI_CCFGR(base), data);
	return CONFIG_OK;
}

static void reg_bit_process(UINT32 *data, UINT32 value, UINT32 mask)
{
	(*data) &= ~mask;
	(*data) |= value;
}

static SINT32 Spi_SetApbReadWireMode(UINT32 base, spi_read_wire_mode_e mode)
{
	if (base != SPI0_REG_BASE)
		return -1;

	UINT32 data = GET_REG(REG_SPI_CCFGR(base));
	switch (mode){
	case STANDARD_READ:
		reg_bit_process(&data, 0 << 8, 7 << 8);  // Dummy Byte: 0 byte
		reg_bit_process(&data, 0 << 2, 3 << 2); // Field Form: all serial
		reg_bit_process(&data, 0 << 0, 3 << 0); // Wire Mode: standard spi
		reg_bit_process(&data, 0 << 4, 7 << 4);  // Dummy Byte: 0 byte
		break;

	case DUAL_OUTPUT:
		reg_bit_process(&data, 0 << 8, 7 << 8);  // Dummy Byte: 0 byte
		reg_bit_process(&data, 3 << 2, 3 << 2);
		reg_bit_process(&data, 1 << 0, 3 << 0);
		reg_bit_process(&data, 0 << 4, 7 << 4);  // Dummy Byte: 0 byte
		break;

	case QUAD_OUTPUT:
		reg_bit_process(&data, 0 << 8, 7 << 8);  // Dummy Byte: 0 byte
		reg_bit_process(&data, 3 << 2, 3 << 2);
		reg_bit_process(&data, 2 << 0, 3 << 0);
		reg_bit_process(&data, 0 << 4, 7 << 4);  // Dummy Byte: 0 byte
		break;

	default:
		printf("wrong mode now....\n");
			FH_SPI_ASSERT(0);
	}
	data |= 1<<13;
	SET_REG(REG_SPI_CCFGR(base), data);
	FH_SPI_DEBUG("reg 0x:%x   data is 0x:%x\n",REG_SPI_CCFGR(base),data);
	return CONFIG_OK;
}

static SINT32 Spi_SetXip(UINT32 base, spi_xip_config_e value)
{

	if (base != SPI0_REG_BASE)
		return -1;
	UINT32 data = GET_REG(REG_SPI_CCFGR(base));
	UINT32 data1 = GET_REG(REG_SPI_OPCR(base));

	if (value == XIP_ENABLE) {
		reg_bit_process(&data, XIP_ENABLE << 11, 1 << 11);
		reg_bit_process(&data1, 0x20 << 20, 0xff << 20);
	} else if (value == XIP_DISABLE) {
		reg_bit_process(&data, XIP_DISABLE << 11, 1 << 11);
		reg_bit_process(&data1, 0xff << 20, 0xff << 20);
	}
	SET_REG(REG_SPI_CCFGR(base), data);
	SET_REG(REG_SPI_OPCR(base), data1);

	return CONFIG_OK;
}

static SINT32 Spi_SetDPI(UINT32 base, spi_dpi_config_e value)
{

	if (base != SPI0_REG_BASE)
		return -1;
	UINT32 data = GET_REG(REG_SPI_OPCR(base));

	reg_bit_process(&data, value << 16, 1 << 16);  // AHB DPI enable
	SET_REG(REG_SPI_OPCR(base), data);

	return CONFIG_OK;
}

static SINT32 Spi_SetQPI(UINT32 base, spi_qpi_config_e value)
{
	if (base != SPI0_REG_BASE)
		return -1;
	UINT32 data = GET_REG(REG_SPI_OPCR(base));

	reg_bit_process(&data, value << 17, 1 << 17);  // AHB QPI enable
	SET_REG(REG_SPI_OPCR(base), data);

	return CONFIG_OK;
}

static SINT32 Spi_TimingConfigure(UINT32 base, UINT32 value)
{
	if (base != SPI0_REG_BASE)
		return -1;
	SET_REG(REG_SPI_TIMCR(base), value);
	return CONFIG_OK;
}

static SINT32 Spi_SetBusBasAddr(UINT32 base)
{
	SET_REG(REG_SPI_BBAR0(base), BUS_BASE_ADDR0);
	SET_REG(REG_SPI_BBAR1(base), BUS_BASE_ADDR1);
	return CONFIG_OK;
}

SINT32 Spi_SetFrameForm(UINT32 base, spi_frame_form_e form)
{
	u32 data;

	Spi_Enable(base, SPI_DISABLE);
	data = GET_REG(REG_SPI_CCFGR(base));
	data &= ~(0x70);
	data |= form;
	SET_REG(REG_SPI_CCFGR(base), data);
	Spi_Enable(base, SPI_ENABLE);

	return 0;
}

SINT32 Spi_SetFieldForm(UINT32 base, spi_field_form_e form)
{
	u32 data;

	Spi_Enable(base, SPI_DISABLE);
	data = GET_REG(REG_SPI_CCFGR(base));
	data &= ~(0x0c);
	data |= form;
	SET_REG(REG_SPI_CCFGR(base), data);
	Spi_Enable(base, SPI_ENABLE);

	return 0;
}

SINT32 Spi_SetDTREnable(UINT32 base, u32 enable)
{
	u32 data;

	Spi_Enable(base, SPI_DISABLE);
	data = GET_REG(REG_SPI_CCFGR(base));
	data = enable ? (data | (0x10000000)) : (data & ~(0x10000000));
	SET_REG(REG_SPI_CCFGR(base), data);
	Spi_Enable(base, SPI_ENABLE);

	return 0;
}

void fh_spic_check_idle(UINT32 base)
{
	SINT32 status;
	if (base != SPI0_REG_BASE)
		return;
	status = Spi_ReadStatus(base);
	//ahb rx fifo not empty..
	FH_SPI_ASSERT((status & 1<<10) == 0);
	//ahb tx fifo empty..
	FH_SPI_ASSERT((status & 1<<9) == 1<<9);
	//apb rx fifo
	FH_SPI_ASSERT((status & 1<<3) == 0);
	//apb tx fifo
	FH_SPI_ASSERT((status & 1<<2) == 1<<2);
	//shift not busy..
	FH_SPI_ASSERT((status & 1) == 0);
}

void spi_hw_init(struct spi_slave *slave, int bus)
{
	fh_spi_controller *fh_spi = to_fh_spi(slave);
	if (!fh_spi)
		return;

	Spi_SetXip(fh_spi->base, XIP_DISABLE);
	Spi_SetDPI(fh_spi->base, DPI_DISABLE);
	Spi_SetQPI(fh_spi->base, QPI_DISABLE);
	Spi_TimingConfigure(fh_spi->base, 0x0);
	Spi_SetBusBasAddr(fh_spi->base);
	Spi_SetApbReadWireMode(fh_spi->base,STANDARD_READ);
	Spi_Clk_Masken(fh_spi->base, 1);
	SPI_SetRxLevel(fh_spi->base, fh_spi->rx_fifo_len - 2);
	slave->mode = spi_info[bus].wire_support | spi_info[bus].swap_support | spi_info[bus].dtr_support;;
}

void spi_bus_change_1_wire(struct spi_slave *slave)
{
	fh_spi_controller *fh_spi = to_fh_spi(slave);

	fh_spic_check_idle(fh_spi->base);
	Spi_Enable(fh_spi->base, SPI_DISABLE);
	Spi_SetApbReadWireMode(fh_spi->base, STANDARD_READ);
	Spi_Enable(fh_spi->base, SPI_ENABLE);
}

void spi_bus_change_2_wire(struct spi_slave *slave)
{
	fh_spi_controller *fh_spi = to_fh_spi(slave);

	fh_spic_check_idle(fh_spi->base);
	Spi_Enable(fh_spi->base, SPI_DISABLE);
	Spi_SetApbReadWireMode(fh_spi->base, DUAL_OUTPUT);
	Spi_Enable(fh_spi->base, SPI_ENABLE);
}

void spi_bus_change_4_wire(struct spi_slave *slave)
{
	fh_spi_controller *fh_spi = to_fh_spi(slave);

	fh_spic_check_idle(fh_spi->base);
	Spi_Enable(fh_spi->base, SPI_DISABLE);
	Spi_SetApbReadWireMode(fh_spi->base,QUAD_OUTPUT);
	Spi_Enable(fh_spi->base, SPI_ENABLE);
}


void spi_wire_configuration_enable(struct spi_slave *slave, int datawidth)
{
	switch (datawidth) {
		case 4:
			spi_bus_change_4_wire(slave);
			break;
		case 2:
			spi_bus_change_2_wire(slave);
			break;
		case 1:
		default:
			spi_bus_change_1_wire(slave);
			break;
	}

}

void spi_wire_configuration_disable(struct spi_slave *slave)
{
	spi_bus_change_1_wire(slave);
}

#ifdef CONFIG_FH_SPI_ECC
void Spi_EccEnable(UINT32 base, u32 enable)
{
	u32 data = 0;

	Spi_Enable(base, SPI_DISABLE);
	data = GET_REG(REG_SPI_ECC_REG0(base));
	data = enable ? (data | 0x1) : (data & ~0x1);
	SET_REG(REG_SPI_ECC_REG0(base), data);
	Spi_Enable(base, SPI_ENABLE);
}

static void Spi_SetEccStepSize(UINT32 base, enum spi_ecc_data_per_step step)
{
	u32 data = 0;

	data = GET_REG(REG_SPI_ECC_REG0(base));
	data &= ~ECC_DATA_STEP_RANGE;
	data |= step;
	SET_REG(REG_SPI_ECC_REG0(base), data);
}

static void Spi_SetEccStrength(UINT32 base, enum spi_ecc_strength strength)
{
	u32 data = 0;

	data = GET_REG(REG_SPI_ECC_REG0(base));
	data &= ~ECC_STRENGTH_RANGE;
	data |= strength;
	SET_REG(REG_SPI_ECC_REG0(base), data);
}

void Spi_SetEccTransferMode(UINT32 base, u32 mode)
{
	u32 data = 0;

	Spi_Enable(base, SPI_DISABLE);
	data = GET_REG(REG_SPI_ECC_REG0(base));
	data = mode ? (data | (0x1 << 5)) : (data & ~(0x1 << 5));
	SET_REG(REG_SPI_ECC_REG0(base), data);
	Spi_Enable(base, SPI_ENABLE);
}

static void Spi_SetSpareSize(UINT32 base, u32 size)
{
	u32 data = 0;

	data = GET_REG(REG_SPI_ECC_REG0(base));
	data &= ~ECC_SPARE_LENGTG_RANGE;
	data |= (size << ECC_SPARE_LENGTH_SHIFT) & ECC_SPARE_LENGTG_RANGE;
	SET_REG(REG_SPI_ECC_REG0(base), data);
}

static void Spi_SetEccPostion(UINT32 base, u32 pos)
{
	u32 data = 0;

	data = GET_REG(REG_SPI_ECC_REG1(base));
	data &= ~ECC_START_POS_RANGE;
	data |= (pos & ECC_START_POS_RANGE);
	SET_REG(REG_SPI_ECC_REG1(base), data);
}

static void Spi_SetPageDataSize(UINT32 base, enum spi_page_data_size size)
{
	u32 data = 0;

	data = GET_REG(REG_SPI_ECC_REG1(base));
	data &= ~ECC_PAGE_DATASIZE_RANGE;
	data |= size;
	SET_REG(REG_SPI_ECC_REG1(base), data);
}

static void Spi_SetPageSpageSize(UINT32 base, enum spi_page_spare_size size)
{
	u32 data = 0;

	data = GET_REG(REG_SPI_ECC_REG1(base));
	data &= ~ECC_PAGE_SPARESIZE_RANGE;
	data |= (size << ECC_PAGE_SPARESIZE_SHIFT) & ECC_PAGE_SPARESIZE_RANGE;
	SET_REG(REG_SPI_ECC_REG1(base), data);
}

static u32 Spi_GetEccStatus(UINT32 base)
{
	return GET_REG(REG_SPI_ECC_STATUS(base));
}

static u32 Spi_GetEccInfo(UINT32 base)
{
	return GET_REG(REG_SPI_NFC_ECC_INFO0(base));
}

void Spi_WaitEccComplete(UINT32 base)
{
	int data;

	do
	{
		data = Spi_GetEccStatus(base);
	} while (!(data & 0x1));
}

int Spi_SetDmaInc(UINT32 base, unsigned int width)
{
	u32 data;

	Spi_Enable(base, SPI_DISABLE);
	data = GET_REG(REG_SPI_CCFGR(base));
	data &= ~(0x3 << 19);
	switch (width) {
		case 32:
#if (defined CONFIG_SOC_FH8862) || (defined CONFIG_SOC_FH8866)
			data |= 0x1 << 19;
#else
			data |= 0x3 << 19;
#endif
			break;
		case 16:
			data |= 0x2 << 19;
			break;
		case 8:
#if (defined CONFIG_SOC_FH8862) || (defined CONFIG_SOC_FH8866)
			data |= 0x3 << 19;
#else
			data |= 0x1 << 19;
#endif
			break;
		case 0:
			data |= 0x0 << 19;
			break;
	}
	SET_REG(REG_SPI_CCFGR(base), data);
	Spi_Enable(base, SPI_ENABLE);

	return 0;
}

void fh_spi_config_ecc(struct spi_slave *slave)
{
	fh_spi_controller *fh_spi = to_fh_spi(slave);
	struct spi_nand_ecc_ctrl ecc = slave->ecc;

	Spi_Enable(fh_spi->base, SPI_DISABLE);
	Spi_SetSpareSize(fh_spi->base, 0);
	Spi_SetEccStepSize(fh_spi->base, ECC_PER_1K_BYTE);
	Spi_SetEccStrength(fh_spi->base, ECC_8BIT_MODE);
	Spi_SetPageSpageSize(fh_spi->base, ecc.total);
	Spi_SetPageDataSize(fh_spi->base, (ecc.size == 0x1000) ? ECC_PAGE_DATASIZE_4K : ECC_PAGE_DATASIZE_2K);
	Spi_SetEccPostion(fh_spi->base, (ecc.total / 2));
	Spi_Enable(fh_spi->base, SPI_ENABLE);
}

void fh_spi_get_ecc_status(struct spi_slave *slave,
unsigned int status,
unsigned int *corrected,
unsigned int *ecc_errors)
{
	unsigned int mask = 0x1F;
	unsigned int data = 0;
	unsigned int ecc_status = 0;
	unsigned int err_status = 0;
	fh_spi_controller *fh_spi = to_fh_spi(slave);

	data = Spi_GetEccStatus(fh_spi->base);
	err_status = Spi_GetEccInfo(fh_spi->base);
	ecc_status = (data & ECC_STATUS_RANGE) >> ECC_STATUS_SHIFT;
	*corrected = (err_status & 0xa) ? (0): (ecc_status & mask);
	*ecc_errors = (err_status & 0xa) ? (1) : (0);
}

void spi_hw_ecc_init(struct spi_slave *slave)
{
	struct spi_nand_ecc_ctrl *ecc = &slave->ecc;

	ecc->engine_type    = NAND_ECC_ENGINE_TYPE_ON_HOST;
	ecc->ecc_config     = fh_spi_config_ecc;
	ecc->get_ecc_status = fh_spi_get_ecc_status;
}
#endif

